require "test/unit"

class Class
  
  def method_missing(method, *args, &block)
    if method.to_s =~ /(attr_.+)_with_defaults/
      attr_defaults(args.first, $1);
    else
      super(method, *args, &block);
    end
  end
  
  def attr_defaults(attrs, method=:attr_accessor)
    self.send(method, *attrs.keys);
    @attrs = attrs;      
  end
  
  alias_method :new2, :new;
  
  def new
    if @attrs
      init = instance_method(:initialize);
      define_method(:initialize) do |*args|
        self.class.send(:instance_variable_get, :@attrs).each do |attr, default_value|
          instance_variable_set("@#{attr}", default_value);      
        end
        init.bind(self).call(*args);
      end
    end
    new2;
  end
  
end

class Person
  attr_reader_with_defaults :name => "Paul";
  attr_writer_with_defaults :money => 100;
  attr_accessor_with_defaults :hair => :brown;
    
end

class AttrDefaultTest < Test::Unit::TestCase
  
  def setup
    @paul = Person.new;
  end
  
  def test_readers
    assert_equal("Paul", @paul.name);
    assert_raise(NoMethodError) do
      @paul.name = "Jack";
    end
  end
  
  def test_writers
    assert_nothing_raised do
      @paul.money = 200;
    end
    assert_raise(NoMethodError) do
      @paul.money;
    end
  end
  
  def test_accessors
    assert_equal :brown, @paul.hair;
    assert_nothing_raised do
      @paul.hair = :blond;
    end
    assert_equal :blond, @paul.hair;
  end
  
end